/**
 *
 * @file parser.c
 *
 * @brief parse a pcap with a supplied filter and generate statistics
 *
 * $Id $
 */

/*
 * This file is part of Tsar.
 *
 * Tsar is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Tsar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Tsar.  If not, see <http://www.gnu.org/licenses/>.
 */



#include <utils.h>
#include <parser.h>


#if defined( PARSER_TEST )
/*
   make
   rm main.o
   gcc -Wall -pedantic -ansi -g -pipe -I./includes -I/usr/include -I/usr/local/include -D_GNU_SOURCE -DPARSER_TEST -c parser.c -o parser.o
   gcc -o tsar_test *.o -L./libs -L/usr/lib -lpcap
   ./tsar_test
*/

void dump_stats( stats_t * st )
{
    unsigned int i;

    printf( "Statistics :\n" );
    printf( "  oldest : %lu\n", (unsigned long)st->oldestPacket.tv_sec );
    printf( "  newest : %lu\n", (unsigned long)st->newestPacket.tv_sec );
        
    printf( "    NEW ENTRY (%s)\n", st->columns[0].nameX );
    printf( "      starting at : %lu\n", (unsigned long)st->columns[0].start.tv_sec );
    printf( "      value       : %lu\n", st->columns[0].value );
    for ( i = 1; i < COLUMNS_PER_GRAPH; i++ )
    {
        printf( "    NEW ENTRY (%s)\n", st->columns[i].nameX );
        printf( "      starting at : %lu (+%lu)\n", (unsigned long)st->columns[i].start.tv_sec, 
                (unsigned long)st->columns[i].start.tv_sec - (unsigned long)st->columns[i-1].start.tv_sec );
        printf( "      value       : %lu\n", st->columns[i].value );
    }

    putchar( '\n' );
}

int main( int argc, char **argv )
{
    stats_t * st;

    if ( argc != 3 )
    {
        fprintf( stderr, "Usage : %s <filename> <pcap filter>\n", argv[0] );
        return 1;
    }

    if ( ( st = make_stats_from_file( argv[1], argv[2] ) ) == NULL )
    {
        fprintf( stderr, "Function failed!!\n" );
        return 2;
    }
    dump_stats( st );
    stats_free( st );

    return 0;
}

#endif



stats_t * make_stats_from_file( char * filename, char * pcapFilter, int * errType )
{
    pcap_t * pcap = NULL;
    stats_t * res = NULL;

    /* Our proud Malloc prefers to die on failure */
    res = ( stats_t * )Malloc( sizeof( stats_t ) );

    /* pcap filter backup for higher level functions */
    res->paramLine = pcapFilter;

    /* Apply the user's filter on the pcap reader */
    if ( ( pcap = set_filter( filename, pcapFilter, errType ) ) == NULL )
    {
        print_error( "Cannot parse file %s\n", filename );
        free( res );
        res = NULL;
    }
    /* generate statistics */
    else if ( compute_stats( res, pcap ) )
    {
        *errType = PARSER_ERROR_STATS;
        print_error( "Cannot compute statistics\n" );
        free( res );
        res = NULL;
    }
    else
    {
        /* Success */
        *errType = PCAP_NO_ERROR;
        print( MESSAGE_NOTICE, "Pcap file successfully parsed\n" );
    }
    
    if ( pcap != NULL )
        pcap_close( pcap );

    return res;
}

pcap_t * set_filter( char * filename, const char * pcapFilter, int * err )
{
    pcap_t * pc = NULL;
    struct bpf_program filter;
    char errbuf[PCAP_ERRBUF_SIZE] = "";

    debug( "[PARSER] processing file %s with bpf expression \"%s\"\n", filename, pcapFilter );

    *err = PCAP_NO_ERROR; /* default */
    if ( ( pc = pcap_open_offline( filename, errbuf ) ) == NULL )
    {
        *err = PCAP_EMPTY_FILE;
        print_error( "[PARSER] %s\n", errbuf );
    }
    /* pcap is able to convert a human readable expression into a real BPF filter... great!! */
    else if ( pcap_compile( pc, &filter, pcapFilter, true, 0 ) == -1 )
    {
        *err = PCAP_INVALID_FILTER;
        print_error( "[PARSER] %s\n", pcap_geterr( pc ) );
        pcap_close( pc );
        pc = NULL;
    }
    /* Apply the previously compiled BPF to the pcap handle */
    else if ( pcap_setfilter( pc, &filter ) == -1 )
    {
        print_error( "%s\n", pcap_geterr( pc ) );
        pcap_freecode( &filter );
        pcap_close( pc );
        pc = NULL;
    }
    else
    {
        pcap_freecode( &filter );
        print( MESSAGE_INFO, "Network traffic dump parser initialized\n" );
    }

    return pc;
}

void stats_free( stats_t * statistics )
{
    if ( statistics != NULL )
    {
        free( statistics );
    }
}

int compute_stats( stats_t * res, pcap_t * pcap )
{
    const unsigned char * pktContent = NULL;
    struct pckListEntry * pckl = NULL;
    struct pcap_pkthdr * h = NULL;
    unsigned long pckCounter = 0;
    bool_t parsingEnded = false;
    struct timeval starting;
    int retVal = 0;

    /* We won't touch packets younger than this in order to be sure to end */
    gettimeofday( &starting, NULL );

    RESET_STATS( res );
    while( Loop && !parsingEnded )
    {
        /* pcap_next_ex let us know an error happened unlike the famous pcap_next */
        switch ( pcap_next_ex( pcap, &h, &pktContent ) )
        {
            /* No more packets to read */
            case    -2  :
                debug( "%s (%s) : No more packets to read\n", __FILE__, __FUNCTION__ );
                parsingEnded = true;
                break;

            /* Error */
            case    -1  :
                print( MESSAGE_CRITICAL, "An error occured while parsing pcap dump (%s)\n", 
                        pcap_geterr( pcap ) );
                parsingEnded = true;
                retVal = 1;
                break;

            case    1   :
                /* packet arrived in the allowed time range */
                if ( arrival_time_in_range( &( h->ts ), &starting ) )
                {
                    add_list_entry( &pckl, h );
                    pckCounter++;
                }
                else
                    parsingEnded = true;
                break;

            /* NOT REACHED */
            default     :
                die( "This case should be unreachable and I can write anything there!\n" );
        }
    }

    if ( pckCounter == 0 )
        print( MESSAGE_WARNING, "Empty pcap file\n" );

    if ( !retVal )
        fill_stats( res, pckl );
    destroy_packets_list( pckl );

    return retVal;
}

void add_list_entry( struct pckListEntry ** list, struct pcap_pkthdr * pktInfo )
{
    struct pckListEntry *entry, *p;

    entry = ( struct pckListEntry * )Malloc( sizeof( struct pckListEntry ) );
    entry->ts = pktInfo->ts;
    entry->next = NULL;

    if ( *list == NULL )
    {
        *list = entry;
    }
    else
    {
        for ( p = *list; p != NULL && p->next != NULL; p = p->next )
        {
            /* nothing to do */
        }
        p->next = entry;
    }
}

void destroy_packets_list( struct pckListEntry * pckl )
{
    struct pckListEntry * head;

    if ( pckl == NULL )
        return;

    head = pckl;
    while ( head->next != NULL )
        delete_list_head( &head );

    Free( head );
}

void delete_list_head( struct pckListEntry ** head )
{
    struct pckListEntry * deleteMe;

    if ( *head == NULL )
        return;

    deleteMe = *head;
    *head = (*head)->next;
    free( deleteMe );
}

int fill_stats( stats_t * res, struct pckListEntry * pktList )
{
    struct pckListEntry *p;

    for ( p = pktList; p != NULL; p = p->next )
    {
        /* Update timing extremities */
        if ( ( res->oldestPacket.tv_sec == 0 && res->oldestPacket.tv_usec == 0 ) 
                ||  p->ts.tv_sec < res->oldestPacket.tv_sec )
        {
            if ( ( res->oldestPacket.tv_sec == 0 && res->oldestPacket.tv_usec == 0 ) 
                ||  p->ts.tv_usec < res->oldestPacket.tv_usec )
            {
                /* Oldest packet of the file seems to be : */
                res->oldestPacket = p->ts;
            }
        }
        else if ( ( res->newestPacket.tv_sec == 0 && res->newestPacket.tv_usec == 0 ) 
                ||  p->ts.tv_sec > res->newestPacket.tv_sec )
        {
            if ( ( res->newestPacket.tv_sec == 0 && res->newestPacket.tv_usec == 0 )
                ||  p->ts.tv_usec > res->newestPacket.tv_usec )
            {
                /* Youngest packet of the file seems to be : */
                res->newestPacket = p->ts;
            }
        }
    }
    set_stats_ranges( res );
    for ( p = pktList; p != NULL; p = p->next )
    {
        store_packet_stats( res, &(p->ts) );
    }
    return 0;
}

bool_t arrival_time_in_range( struct timeval * pckTv, struct timeval * startingTv )
{
    /* We want our packets to be older than the date when we started parsing the file */
    return ( pckTv->tv_sec <= startingTv->tv_sec );
}

void set_stats_ranges( stats_t * res )
{
    double singleRange;
    char * p;
    char startAscii[30] = "";
    char stopAscii[30] = "";
    time_t stop;
    unsigned int i;

    /* Divide capture time into COLUMNS_PER_GRAPH number of ranges */
    singleRange = ((double)( res->newestPacket.tv_sec - res->oldestPacket.tv_sec )) / ((double)COLUMNS_PER_GRAPH);

    for ( i = 0; i < COLUMNS_PER_GRAPH; i++ )
    {
        res->columns[i].start.tv_sec = res->oldestPacket.tv_sec + ( (double)i * singleRange );
        /* craft the column's name */
        stop = res->columns[i].start.tv_sec + singleRange;
        ctime_r( ( time_t * )&( res->columns[i].start.tv_sec ), startAscii );
        ctime_r( &stop, stopAscii );

        snprintf(
                res->columns[i].nameX, 
                sizeof( res->columns[i].nameX ),
                "%s- %s",
                startAscii,
                stopAscii );
        /* Replace ctime's '\n' by spaces */
        for ( p = res->columns[i].nameX; *p != '\0'; p++ )
        {
            if ( *p == '\n' )
                *p = ' ';
        }
    }
}

void store_packet_stats( stats_t * res, struct timeval * arrivalTime )
{
    unsigned int i;

    /* look for the good segment to update */
    for ( i = 0; i < ( COLUMNS_PER_GRAPH - 1 ); i++ )
    {
        if ( ( arrivalTime->tv_sec >= res->columns[i].start.tv_sec ) 
                && ( arrivalTime->tv_sec < res->columns[i + 1].start.tv_sec ) )
        {
            res->columns[i].value++;
            return;
        }
    }
    /* If we are here : then the packet has to be stored in the last segment */
    res->columns[COLUMNS_PER_GRAPH - 1].value++;
}

